/*****************************************************\
* Copyright (c) 2020 The game2d Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/game2d.git
* Email:          505544956@qq.com
* FileName:       core/game2d.h
* Create Time:    2020/03/08 16:32:18
* Description:    /
\****************************************************/

#ifndef __GAME2D_H__
#define __GAME2D_H__

#include <functional>
#include "game.h"

#define G2D_INVALDATE_INDEX (SDL_MAX_UINT32)

namespace g2d {
  class Game2D;

  class RefCounter {
    public:
      Uint32 incref() { return ++refcount_; }
      Uint32 decref() { --refcount_; if (refcount_ == 0) delete this; return refcount_; }
      Uint32 ref_count() const { return refcount_; }
    protected:
      RefCounter() : refcount_(1) {}
      virtual ~RefCounter() = default;
    private:
      Uint32 refcount_;
  };

  template <typename T>
  class ContainerT {
    protected:
      Result add(T *d, Uint32 idx = G2D_INVALDATE_INDEX);
    public:
      void erase(const char *id);
      void erase(Uint32 idx);
      void erase(T *d);
      void clear();
      Uint32 count() const;
      T* at(Uint32 idx) const;
      T* get(const char *id) const;
      Uint32 index(T *d) const;
    protected:
      std::vector<T*> datas_;
  };

  class Object : public RefCounter {
    protected:
      Object() : g2d_(0), visible_(true), enabled_(true) {}
      virtual ~Object() = default;
    protected:
      virtual Result init(Game2D *game, const char *id) {
        if (!game || !id) return Result::E_InvalidParam;
        g2d_ = game; id_ = id;
        return Result::S_OK;
      }
      virtual void uninit() {}
      virtual Result enter(Font &f, Texture &t, Audio &a) = 0;
      virtual Result exit(Font &f, Texture &t, Audio &a) = 0;
      virtual void update(Input &inp, Audio &aud) = 0;
      virtual void render(Renderer &r) = 0;
    public:
      Game2D* game2d() const { return g2d_; }
      const char* id() const { return id_.c_str(); }
      void set_visible(bool visible) { visible_ = visible; }
      bool visible() const { return visible_; }
      void set_enable(bool enable) { enabled_ = enable; }
      bool enabled() const { return enabled_; }
    protected:
      Game2D *g2d_;
      std::string id_;
      bool visible_;
      bool enabled_;
  };

  class GameObject : public Object, public ContainerT<GameObject> {
      friend class Layer;
    protected:
      GameObject(bool load_res_on_enter);
      virtual ~GameObject();
    public:
      virtual void update(Input &inp, Audio &aud) override;
      virtual void render(Renderer &r) override;
    protected:
      virtual Result init(Game2D *game, const char *id, Layer *layer);
      virtual void uninit() override;
      virtual Result enter(Font &f, Texture &t, Audio &a) override;
      virtual Result exit(Font &f, Texture &t, Audio &a) override;
      virtual Result load_resource(Font &f, Texture &t, Audio &a) { return Result::S_FunctionNotOverride; }
      virtual void unload_resource(Font &f, Texture &t, Audio &a) {}
    protected:
      Result add(GameObject *d, Uint32 idx = G2D_INVALDATE_INDEX);
    public:
      bool load_res_on_enter() const { return load_res_on_enter_; }
      Layer* layer() const { return layer_; }
      GameObject* parent() const { return parent_; }
    public:
      GameObject* find_gobj(const char *id, bool child_first = false, bool order_rbegin = false);
      GameObject* create_gobj(const char *type, const char *id = "", Uint32 idx = G2D_INVALDATE_INDEX, bool load_res_on_enter = true);
      GameObject* get(const char *id, bool create = false, const char *type = 0, Uint32 idx = G2D_INVALDATE_INDEX);
    protected:
      bool load_res_on_enter_;
      Layer *layer_;
      GameObject *parent_;
  };

  class Layer : public Object, public ContainerT<GameObject> {
      friend class Scene;
      friend class Game2D;
    protected:
      Layer() = default;
      virtual ~Layer() { clear(); }
    protected:
      virtual Result enter(Font &f, Texture &t, Audio &a) override;
      virtual Result exit(Font &f, Texture &t, Audio &a) override;
      virtual void update(Input &inp, Audio &aud) override;
      virtual void render(Renderer &r) override;
    public:
      GameObject* find_gobj(const char *id, bool child_first = false, bool order_rbegin = false) const;
      GameObject* create_gobj(const char *type, const char *id = "", Uint32 idx = G2D_INVALDATE_INDEX, bool load_res_on_enter = true);
      GameObject* get(const char *id, bool create = false, const char *type = 0, Uint32 idx = G2D_INVALDATE_INDEX);
  };
  
  class Scene : public Object, public ContainerT<Layer> {
      friend class Game2D;
    protected:
      Scene() = default;
      virtual ~Scene() { clear(); }
    protected:
      virtual Result enter(Font &f, Texture &t, Audio &a) override;
      virtual Result exit(Font &f, Texture &t, Audio &a) override;
      virtual void update(Input &inp, Audio &aud) override;
      virtual void render(Renderer &r) override ;
    public:
      GameObject* find_gobj(const char *id, bool child_first = false, bool order_rbegin = false) const;
      Layer* create_layer(const char *id, Uint32 idx = G2D_INVALDATE_INDEX);
      Layer* get(const char *id, bool create = false, Uint32 idx = G2D_INVALDATE_INDEX);
  };

  class Game2D : public Game {
      friend class Layer;
      friend class GameObject;
    public:
      Game2D() : current_scene_(0) {}
      Game2D(std::function<GameObject*(const char *type, bool load_res_on_enter)> create_gobj_callback)
        : fun_create_gobj_(create_gobj_callback)
        , current_scene_(0) {}
    public:
      virtual void uninit();
    public:
      Scene* load_scene(const char *id);
      void unload_scene(const char *id);
      void unload_scene(const Scene *scene);
      void clear_scene();
      Uint32 scene_count() const;
      Scene* get_scene(const char *id, bool create = false);
      Layer* get_layer(const char *scene, const char *layer, bool create = false);

      Result change_scene(const char *id);
      Result change_scene(Scene *scene);
      Scene* current_scene() const;
    public:
      Result load_res_file(const char *xml);
      Result load_res_buffer(const char *xml, Uint32 len);
      Result load_scene_file(const char *xml);
      Result load_scene_buffer(const char *xml, Uint32 len);
    protected:
      GameObject* create_gobj(const char *type, bool load_res_on_enter) const;
    protected:
      virtual void update(Input &inp, Audio &aud) override;
      virtual void render(Renderer &r) override;
    protected:
      std::function<GameObject*(const char *type, bool load_res_on_enter)> fun_create_gobj_;
      std::map<std::string, Scene*> scenes_;
      Scene *current_scene_;
  };
} // end namespace g2d

//////////////////////////////////////
// Implement
namespace g2d {
  ////////////////////////
  template <typename T>
  Result ContainerT<T>::add(T *d, Uint32 idx = G2D_INVALDATE_INDEX) {
    auto i = index(d);
    auto c = datas_.size();
    if (i < c) return Result::E_Existed;
    if (idx < c) {
      datas_.insert(datas_.begin() + idx, d);
    } else {
      datas_.push_back(d);
    }
    d->incref();
    return Result::S_OK;
  }
  template <typename T>
  void ContainerT<T>::erase(const char *id) {
    if (!id || !*id) return;
    for (auto it = datas_.begin(); it != datas_.end(); ++it) {
      if (strcmp((*it)->id(), id) == 0) {
        (*it)->decref();
        datas_.erase(it);
        break;
      }
    }
  }
  template <typename T>
  void ContainerT<T>::erase(Uint32 idx) {
    if (idx >= datas_.size()) return;
    auto it = datas_.begin() + idx;
    (*it)->decref();
    datas_.erase(it);
  }
  template <typename T>
  void ContainerT<T>::erase(T *d) {
    if (!d) return;
    for (auto it = datas_.begin(); it != datas_.end(); ++it) {
      if (*it == d) {
        d->decref();
        datas_.erase(it);
        break;
      }
    }
  }
  template <typename T>
  void ContainerT<T>::clear() {
    for (auto v : datas_) {
      v->decref();
    }
    datas_.clear();
  }
  template <typename T>
  Uint32 ContainerT<T>::count() const { return datas_.size(); }
  template <typename T>
  T* ContainerT<T>::at(Uint32 idx) const {
    if (idx < datas_.size()) return datas_.at(idx);
    return 0;
  }
  template <typename T>
  T* ContainerT<T>::get(const char *id) const {
    if (!id || !*id) return 0;
    for (auto v : datas_) {
      if (strcmp(v->id(), id) == 0)
        return v;
    }
    return 0;
  }
  template <typename T>
  Uint32 ContainerT<T>::index(T *d) const {
    if (!d) return G2D_INVALDATE_INDEX;
    for (auto it = datas_.begin(); it != datas_.end(); ++it)
      if (*it == d) return (it - datas_.begin());
    return G2D_INVALDATE_INDEX;
  }
}

#endif // __GAME2D_H__